package com.bum.sock.poll.bo;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.bum.api.core.exp.om.Metas;
import com.bum.api.core.exp.om.Params;
import com.bum.sock.poll.bo.MessageBO.MessageEventListener;
import com.bum.sock.poll.om.Message;
import com.bum.sock.poll.om.Message.Receiver;
import com.bum.sock.poll.om.MessageData;
import com.bum.sock.poll.util.ChainMap;
import com.google.gson.Gson;

public class ChatBO implements ApplicationContextAware {
	Log							log				= LogFactory.getLog(getClass());

	public static final int		APP_CHAT		= 1;

	public static final int		FUNC_CONNECT	= 1;
	public static final int		FUNC_PAIR		= 2;
	public static final int		FUNC_UNPAIR		= 3;
	public static final int		FUNC_SEND_MSG	= 4;
	public static final int		FUNC_PING		= 5;
	public static final int		FUNC_DISCONNECT	= 6;

	private Gson				gson;
	private MessageBO			messageBO;

	private Set<String>			pool			= new HashSet<String>();
	private Set<String>			waiting			= new HashSet<String>();

	private Map<String, String>	pair			= new HashMap<String, String>();

	private Map<String, Long>	pingMs			= new HashMap<String, Long>();

	public void process(Params params, Metas metas) {
		int act = NumberUtils.toInt(params.getValue("act"), 0);
		String wsc = params.getValue("wsc");

		String ip = metas.get(Metas.CLIENT_IP);
		log.debug("\n" + ip + " : " + gson.toJson(params) + "\n" + gson.toJson(pool) + "\n" + gson.toJson(pair) + "\n");

		if (act <= 0 || StringUtils.isBlank(wsc)) {
			return;
		}

		switch (act) {
			case FUNC_CONNECT:
				funcConnect(wsc);
				return;

			case FUNC_PAIR:
				funcPair(wsc);
				return;

			case FUNC_UNPAIR:
				funcUnPair(wsc, false);
				return;

			case FUNC_SEND_MSG:
				funcSendMessage(wsc, params.getValue("text"));
				return;

			case FUNC_PING:
				funcPing(wsc, params.getValue("id"));
				return;

			case FUNC_DISCONNECT:
				funcDisconnect(wsc, params.getValue("id"));
				return;

			default:
				break;
		}
	}

	private void addToPool(String user) {
		if (user != null) {
			pool.add(user);
		}
	}

	private void removeFromPool(String user) {
		if (user != null) {
			pool.remove(user);
		}
	}

	private void disconnect(String wsc, String id) {
		if (pair.containsKey(wsc)) {
			funcUnPair(wsc, true);
		}

		waiting.remove(wsc);
		removeFromPool(wsc);
	}

	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		messageBO.addMessageEventListener(new MessageEventListener() {
			@Override
			public void onEventEnd(String wsc, String id) {
				disconnect(wsc, id);
			}
		});
	}

	private void funcPing(String wsc, String id) {
		pingMs.put(wsc, System.currentTimeMillis() + 20000L);

		String json = gson.toJson("ping");

		Message message = new Message();
		message.receivers.add(new Receiver(wsc, id));
		message.json = json;

		messageBO.send(message);
	}

	private void funcConnect(String wsc) {
		if (!pool.contains(wsc)) {
			if (pair.containsKey(wsc)) {
				// ngắt cuộc nói chuyện cũ
				funcUnPair(wsc, false);
			}

			addToPool(wsc);
		}

		MessageData msgData = new MessageData(APP_CHAT, FUNC_CONNECT);
		String json = gson.toJson(msgData);

		Message message = new Message();
		message.receivers.add(new Receiver(wsc));

		System.out.println("Connect: " + gson.toJson(pool) + "\n" + gson.toJson(pair));

		message.json = json;

		messageBO.send(message);

		Iterator<String> iter = waiting.iterator();
		if (iter.hasNext()) {
			String stranger = iter.next();
			if (stranger != null) {
				funcPair(stranger);
			}
		}
	}

	private void funcDisconnect(String wsc, String id) {
		disconnect(wsc, id);
	}

	private void funcPair(String wsc) {
		// lấy người này ra khỏi pool trước
		removeFromPool(wsc);
		waiting.remove(wsc);

		String stranger = null;
		Iterator<String> iterator = pool.iterator();
		while (iterator.hasNext() && stranger == null) {
			stranger = iterator.next();
			if (stranger != null) {
				Long ms = pingMs.get(stranger);
				// đá kết nối đã "chết"
				if (ms == null || System.currentTimeMillis() > ms) {
					messageBO.remove(stranger);
					removeFromPool(stranger);
					waiting.remove(stranger);

					stranger = null;
				}
			}
		}

		System.out.println("Pair: " + wsc + " " + stranger);

		if (stranger == null) {
			// không tìm được bạn, trở ra chờ
			addToPool(wsc);

			// thêm vào ds ưu tiên
			waiting.add(wsc);
			return;
		} else {
			removeFromPool(stranger);
		}

		pair(wsc, stranger);

		MessageData msgData = new MessageData(APP_CHAT, FUNC_PAIR);
		msgData.data = ChainMap.get().put("message", "Trước lạ sau quen nhé!").map();
		String json = gson.toJson(msgData);

		Message message = new Message();
		message.receivers.add(new Receiver(wsc));
		message.receivers.add(new Receiver(stranger));
		message.json = json;

		messageBO.send(message);
	}

	private void funcUnPair(String wsc, boolean disconnect) {
		String stranger = pair.get(wsc);
		if (stranger == null) {
			return;
		}

		unPair(wsc, stranger);

		addToPool(stranger);
		if (!disconnect) {
			addToPool(wsc);
		}

		MessageData msgData = new MessageData(APP_CHAT, FUNC_UNPAIR);
		String json = gson.toJson(msgData);

		Message message = new Message();
		if (!disconnect) {
			message.receivers.add(new Receiver(wsc));
		}
		message.receivers.add(new Receiver(stranger));
		message.json = json;

		messageBO.send(message);
	}

	private void funcSendMessage(String wsc, String textMsg) {
		String stranger = pair.get(wsc);

		log.debug(MessageFormat.format("{0} => {1} : {2}", wsc, stranger, textMsg));

		MessageData msgData = new MessageData(APP_CHAT, FUNC_SEND_MSG);
		msgData.data = ChainMap.get().put("message", textMsg).map();
		String json = gson.toJson(msgData);

		Message message = new Message();
		message.receivers.add(new Receiver(stranger));
		message.json = json;

		messageBO.send(message);
	}

	private void pair(String user, String stranger) {
		pair.put(user, stranger);
		pair.put(stranger, user);
	}

	private void unPair(String user, String stranger) {
		pair.remove(user);
		pair.remove(stranger);
	}

	/**
	 * SETTERS
	 */
	public void setGson(Gson gson) {
		this.gson = gson;
	}

	public void setMessageBO(MessageBO messageBO) {
		this.messageBO = messageBO;
	}

}
